X に書ききれないことはここに

X に書ききれないことはここに

ESP32 で LG のテレビを叩く

概要

LG のテレビはスマホアプリなどネットワーク経由で操作ができるので、実際に ESP32 を使って wifi 経由での操作を試してみました。

通信プロトコル

詳細は公開されていないのですが WebSocket でポート 3000 に JSON フォーマットでメッセージを送ればいいようです。lgtv2 のソースコードから読み取り&実際に通信して挙動を確認しました。コマンド一覧はあるのにコマンド毎の指定値は不明なので検索するなりしてなんとか。

また lgtv2 の README には You need to allow “LG Connect Apps” on your TV とありますが最近のモデルだと接続するときにテレビにポップアップが出るのでそのときに OK すればよさそうです。

用意したもの

  • ESP32 の開発基板 + 配線 (前回と同じ)
  • Arduino IDE + esp32 + WebSockets
  • lgtv2 のソースコード (仕様)

サンプルコード

Pin14 につながったボタンを押すと HDMI 1 に入力を切り替えるサンプル。1回しか実行しないので何度も実行したい場合は要リセット。クライアントIDを保持しないので毎回リモコンで OK する必要があります。クライアントIDの扱いに関しては lgtv2 のソースコードを確認してください。

  • esp32, lgtv, subnet, ssid, password, register_msg は要書き換え
#include <arduino.h>
#include <wifi.h>
#include <websocketsclient.h>

String switch_msg(R"({"id":"abcdef123457","type":"request","uri":"ssap://tv/switchInput","payload":{"inputId":"HDMI_1"}})");
String register_msg(R"({"id":"abcdef123456","type":"register","payload":
/// ここに lgtv2/pairing.json の中身を貼り付け ///
})");

const IPAddress esp32(192,168,0,4);
const IPAddress lgtv(192,168,0,5);
const IPAddress subnet(255,255,255,0);
const char ssid[] = "SSID";
const char password[] = "PASSWORD";

WebSocketsClient webSocket;

enum State {
  STATE_DISCONNECTED,
  STATE_CONNECTING,
  STATE_CONNECTED,
  STATE_REGISTERED,
  STATE_COMMAND,
  STATE_DISCONNECTING,
  STATE_HALT
};

State g_state = STATE_DISCONNECTED;

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_CONNECTED:
      Serial.printf("Connected\r\n");
      g_state = STATE_CONNECTED;
      webSocket.sendTXT(register_msg);
      break;

    case WStype_TEXT:
      Serial.printf("Text: %s\r\n", payload);

      if(g_state == STATE_CONNECTED){
        const char reg_response[] = R"({"type":"registered",)";
        if(strncmp((char *)payload, reg_response, 20) == 0){
          g_state = STATE_COMMAND;
          webSocket.sendTXT(switch_msg);
        }
      }else if(g_state == STATE_COMMAND){
        g_state = STATE_DISCONNECTING;
        webSocket.disconnect();
      }
      break;

    case WStype_DISCONNECTED:
      Serial.printf("Disconnected\r\n");
      g_state = STATE_HALT;
      break;

    default:
      break;
  }
}

volatile bool g_irq0 = false;
void ISR0(){
  g_irq0 = true;
}

void setup() {
  Serial.begin(115200);

  register_msg.replace("\r", "");
  register_msg.replace("\n", "");

  pinMode(14, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(14), ISR0, FALLING);

  WiFi.config(esp32, esp32, subnet);
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
  if(g_state == STATE_DISCONNECTED){
    if(g_irq0){
      g_state = STATE_CONNECTING;
      webSocket.begin(lgtv, 3000);
      webSocket.onEvent(webSocketEvent);
      webSocket.setReconnectInterval(5000);
    }
  }else if(g_state != STATE_HALT){
    webSocket.loop();
  }
}

参考

そのほか

WebSocketsClient のライブラリはエラーを吐けない (返り値 void) ので、うまく動作しないときはパケットキャプチャして WebSocket のパケットが飛んでいるか確認したほうが良さそうです。パケットが飛んでないことがよくありました。